package com.zhike.service.im.manager;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.zhike.service.im.concurrent.CommonConcurrentExecutor;
import com.zhike.service.messages.EsSearchService;
import com.zhike.service.messages.IACKMessageRepository;
import com.zhike.service.messages.IChatSessionService;
import com.zhike.service.messages.IHistoryMessageRepository;
import com.zhike.utils.cache.DeviceInfoCacheProvider;
import com.zhike.utils.cache.UsersInfoCacheProvider;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import java.util.Vector;
import lombok.extern.slf4j.Slf4j;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.zhike.common.constant.ChatType;
import com.zhike.common.dto.ChatSessionMessageDTO;
import com.zhike.common.dto.MsgBody4Group;
import com.zhike.common.dto.MsgBodyRoot;
import com.zhike.common.enums.YesNoEnum;
import com.zhike.common.exception.ZkimException;
import com.zhike.common.model.Device;
import com.zhike.common.repository.HistoryMessage;
import com.zhike.common.vo.UserInfo4Cache;

/**
 * 用户聊天消息记录（收集）管理器.
 * 
 * <p>
 * <b>用户聊天消息在什么地方收集合适？</b><br>
 * <br>
 * 详见“RainbowChat_pro_v4关键设计思路备忘.txt”。
 * 
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @since 2.5
 */
// TODO 优化建议：不要逐条都提交到数据库，比如可以当到了100条才一次性update到数据库哦（这个逻辑需要注意边界问题，比如非正常关闭等）。
@Component
@Slf4j
public class MessageRecordManager// UserMsgCollectHelper.java
{

	@Resource
	private IHistoryMessageRepository historyMessageRepository;

	@Resource
	private IACKMessageRepository ackMessageRepository;

	@Autowired
	private DeviceInfoCacheProvider deviceInfoCacheProvider;

	@Autowired
	private UsersInfoCacheProvider usersInfoCacheProvider;

	@Autowired
	private IChatSessionService chatSessionService;

	// 缓冲指纹码
	public List<String> fgs = new Vector<>();

	/**
	 * 异步插入正常聊天记录到数据库中（当要仅处理正常聊天消息时，请使用本方法）。
	 * <p>
	 * <b>注意：</b>本方法仅用于正常的好友聊天、陌生人聊天、群聊天消息处理，不
	 * 支持群聊指令等消息体对象不是 {@link MsgBodyRoot}子类的情况下）。
	 * 
	 * @param to                  是设备ID
	 * @param originalDataContent 原始消息内容，即此条消息对应的Protocal协议包的dataContent内容
	 * @param fingerprint         消息指纹码（即消息唯一id）
	 */
	public void colectChattingMsgAsync(final String to, final String originalDataContent, final int msgType,
			final String fingerprint) {
		if (originalDataContent != null) {
			// 解析原始的聊天数据（该数据是所有聊天消息的DTO对象的JSON文本）
			MsgBodyRoot tm = MsgBodyRoot.parseFromSender(originalDataContent);
			if (tm == null) {
				log.error("异步插入聊天记录时未能解析原始数据 {}", originalDataContent);
				return;
			}

			if (tm != null && tm.getF() != null) {
				Device device = deviceInfoCacheProvider.get(to);
				if (device == null) {
					log.warn("{} 不在设备缓存中", to);
					return;
				}
				UserInfo4Cache uc = usersInfoCacheProvider.get(device.getFakeUid());
				if (uc == null) {
					log.warn("设备:{} 关联FakeUid:{} 不在缓存中", device.getDeviceNo(), device.getFakeUid());
					return;
				}

				if (tm.getF().equals(uc.getFakeUid())) {
					return;
				}
				final String sessionId;
				ChatSessionMessageDTO cs1 = new ChatSessionMessageDTO();
				cs1.setFromUid(tm.getF());
				cs1.setToUid(uc.getFakeUid());
				cs1.setChatType(String.valueOf(tm.getCy()));
				cs1.setMsgType(String.valueOf(tm.getTy()));
				cs1.setLastMsg(tm.getM());
				cs1.setLastTime(new Date());
				cs1.setFp(fingerprint);
				chatSessionService.updateUserChatSession(cs1);
				ChatSessionMessageDTO cs2 = ObjectUtil.clone(cs1);
				cs2.setFromUid(uc.getFakeUid());
				cs2.setToUid(tm.getF());
				sessionId = chatSessionService.updateUserChatSession(cs2);

				// 处理ID from_uid dest_uid最终需要转换fakeUid
				UserInfo4Cache uic = usersInfoCacheProvider.get(tm.getF());
				if (uic == null) {
					log.warn("{} 的缓存数据未获取到", tm.getF());
					return;
				}

				Device dd = deviceInfoCacheProvider.get(to);
				if (dd == null) {
					log.warn("{} 的缓存数据未获取到", to);
					return;
				}

				CommonConcurrentExecutor.getInstance().execute(new Runnable() {
					@Override
					public void run() {
						colectMsgAsync(sessionId, uic.getFakeUid(), dd.getFakeUid(), tm.getCy(),
								tm.getTy(),
								tm.getM(),
								fingerprint);
					}
				});
			}
		}
	}

	/**
	 * 保存群消息
	 * 
	 * @param sessionId
	 * @param from_uid
	 * @param dest_uid
	 * @param groupId
	 * @param chat_type
	 * @param msg_type
	 * @param msg_content
	 * @param fingerprint
	 * @return
	 */
	public HistoryMessage colectGroupMsgAsync(String sessionId, final String from_uid, final String dest_uid,
			final String groupId,
			final int chat_type,
			final int msg_type,
			final String msg_content, final String fingerprint) {
		log.error("MongoDB存储消息：sessionId: {}, msg_content: {}, fingerprint:{}", sessionId, msg_content, fingerprint);
		if (StringUtils.isBlank(fingerprint)) {
			return null;
		}
		if (fgs.contains(fingerprint)) {
			fgs.clear();
			return null;
		}
		fgs.add(fingerprint);

		HistoryMessage historyMessage = new HistoryMessage();
		historyMessage.setId(IdUtil.getSnowflakeNextIdStr());
		historyMessage.setSessionId(sessionId);
		historyMessage.setSenderUid(from_uid);
		historyMessage.setReceiver(dest_uid);
		historyMessage.setGroupId(groupId);
		historyMessage.setChatType(chat_type);
		historyMessage.setMsgType(msg_type);
		historyMessage.setContent(msg_content);
		historyMessage.setFp(fingerprint);
		historyMessage.setDeleted(YesNoEnum.NO.getValue());
		historyMessage.setRevoked(YesNoEnum.NO.getValue());
		historyMessage.setTimestamp(new Date());
		historyMessageRepository.addHistoryMessage(historyMessage);
		return historyMessage;
	}

	/**
	 * 异步插入正常聊天记录、指令等数据到数据库中（当不需要区分是不是正常聊天消息时，可使用本方法）。
	 * <p>
	 * 本方法是通用方法，只要是符合聊天记录数据模型的字段定义即可，不一定非得是正常聊天消息内容。
	 * 
	 * @param deviceNo          消息发送者uid
	 * @param dest_uid          接收目标id（可能是接收者uid或群聊id）
	 * @param chat_type         聊天模式，详见：{@link MsgBodyRoot}中的常量定义
	 * @param msg_type          消息类型
	 * @param msg_content       消息内容（可能是JSON字串表示的对象）
	 * @param fingerprint       消息指纹码（即唯一id）
	 * @param parent_fp         本字段只有在聊天模式类型chatType为“2
	 *                          群聊”时有意义，表示群聊消息扩散写前原始消息的指纹码（目前主要用于消息“撤回”功能时）
	 * @param at                本字段只有在聊天模式类型chatType为“2 群聊”时有意义，存放的是“@”对象列表
	 * @param quote_fp          引用消息的指纹码
	 * @param quote_sender_uid  引用消息的发送者uid
	 * @param quote_sender_nick 引用消息的发送者昵称
	 * @param quote_status      引用消息的状态（0 原消息正常，1 原消息已被撤回。默认0）
	 * @param quote_content     引用消息的内容
	 * @param quote_type        引用消息的类型
	 */
	public HistoryMessage colectMsgAsync(String sessionId, final String from_uid, final String dest_uid,
			final int chat_type,
			final int msg_type,
			final String msg_content, final String fingerprint) {
		if (StringUtils.isBlank(fingerprint)) {
			log.debug("指纹码是空, 不能进行消息存储");
			return null;
		}

		if (fgs.contains(fingerprint)) {
			fgs.clear();
			return null;
		}
		fgs.add(fingerprint);

		HistoryMessage historyMessage = null;
		try {
			historyMessage = new HistoryMessage();
			historyMessage.setId(IdUtil.getSnowflakeNextIdStr());
			historyMessage.setSessionId(sessionId);
			historyMessage.setSenderUid(from_uid);
			historyMessage.setReceiver(dest_uid);
			historyMessage.setChatType(chat_type);
			historyMessage.setMsgType(msg_type);
			historyMessage.setContent(msg_content);
			historyMessage.setFp(fingerprint);
			historyMessage.setDeleted(YesNoEnum.NO.getValue());
			historyMessage.setRevoked(YesNoEnum.NO.getValue());
			historyMessage.setTimestamp(new Date());
			historyMessageRepository.addHistoryMessage(historyMessage);
			return historyMessage;
		} catch (Exception e) {
			throw new ZkimException(e.getMessage());
		}
	}

	/**
	 * 适用于服务端发送客户端的消息存储及会话更新
	 * 逐条保存消息并更新会话，在循环语句中调用时注意sessionId, fp要唯一
	 * //TODO 批量保存
	 * 
	 */
	public void saveMessageAndUpdateSession(String sessionId, String from_uid, String dest_uid, int chat_type,
			int msg_type, String msg_content, String fingerprint) {

		HistoryMessage hm = colectMsgAsync(sessionId, from_uid, dest_uid, chat_type, msg_type,
				msg_content,
				fingerprint);
		if (hm != null) {
			ChatSessionMessageDTO dto = new ChatSessionMessageDTO();
			dto.setSessionId(hm.getSessionId());
			dto.setFromUid(from_uid);
			dto.setToUid(hm.getReceiver());
			dto.setChatType(String.valueOf(hm.getChatType()));
			dto.setMsgType(String.valueOf(hm.getMsgType()));
			dto.setFp(hm.getFp());
			dto.setLastMsg(msg_content);
			chatSessionService.updateUserChatSession(dto);

			ChatSessionMessageDTO dto2 = ObjectUtil.clone(dto);
			dto2.setToUid(from_uid);
			dto2.setFromUid(hm.getReceiver());
			chatSessionService.updateUserChatSession(dto2);
		}
	}

}
